﻿/*
 * Adapted from http://www.eidias.com/Blog/2013/7/13/utilizing-inotifydataerrorinfo-in-wpf-mvvm-app
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.CompilerServices;

namespace Smokey.Shared.DataTransferObjects {
    public abstract class DataTransferObjectBase : INotifyDataErrorInfo, INotifyPropertyChanged {
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        private Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>();
        public event PropertyChangedEventHandler PropertyChanged;

        public bool IsValid {
            get { return !this.HasErrors; }
        }

        public IEnumerable GetErrors(string propertyName) {
            if (!string.IsNullOrEmpty(propertyName)) {
                if (errors.ContainsKey(propertyName)
                    && (errors[propertyName] != null)
                    && errors[propertyName].Count > 0) {
                    return errors[propertyName].ToList();
                } else {
                    return null;
                }
            } else {
                return errors.SelectMany(err => err.Value.ToList());
            }
        }

        public bool HasErrors {
            get {
                return errors.Any(propErrors => propErrors.Value != null &&
                    propErrors.Value.Count > 0);
            }
        }

        public void OnErrorsChanged(string propertyName) {
            if (ErrorsChanged != null) {
                ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
            }
        }

        public void ValidateProperty(object value, [CallerMemberName] string propertyName = null) {
            var validationContext = new ValidationContext(this, null, null);
            validationContext.MemberName = propertyName;
            var validationResults = new List<ValidationResult>();
            Validator.TryValidateProperty(value, validationContext, validationResults);

            // clear prevous errors
            if (errors.ContainsKey(propertyName)) {
                errors.Remove(propertyName);
            }

            OnErrorsChanged(propertyName);
            HandleValidationResults(validationResults);
        }

        public void Validate() {
            var validationContext = new ValidationContext(this, null, null);
            var validationResults = new List<ValidationResult>();
            Validator.TryValidateObject(this, validationContext, validationResults, true);

            // clear all previous errors
            var propNames = errors.Keys.ToList();
            errors.Clear();
            propNames.ForEach(pn => OnErrorsChanged(pn));
            HandleValidationResults(validationResults);
        }

        public void HandleValidationResults(List<ValidationResult> validationResults) {
            var resultsByPropNames = validationResults
                .Select(r => r.MemberNames
                .GroupBy(key => key, element => r,
                (key, items) => new { 
                    Key = key, ErrorMessages = items.Select(e => e.ErrorMessage).ToList() 
                }))
                .SelectMany(g => g);

            foreach (var prop in resultsByPropNames) {
                var messages = prop.ErrorMessages;

                errors.Add(prop.Key, messages);
                OnErrorsChanged(prop.Key);
            }
        }

        public void OnPropertyChanged(string name) {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) {
                Validate();
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}
